#include <jni.h>
#include <android/log.h>
#include <oboe/Oboe.h>
#include <sys/stat.h>

//signed 16bit little endian, stereo 44100Hz

typedef struct{
    void* buffer;
    uint32_t size;
}PlayCache;

typedef struct{
    uint32_t indexOfCache;
    uint32_t readBytes;
}PlayStatus;

void init(int32_t framesPerBuffer,int32_t sampleRate);
void allocFromFile(const char* fileName,PlayCache* playCache);
bool mixData(void* audioDataBuffer,int32_t numFrames,PlayStatus* playStatus);

static int32_t playCachesCapacity;
static PlayCache* playCaches;
static std::shared_ptr<oboe::AudioStream> stream;
static std::vector<PlayStatus> playStatusVector;

class SoundCallback : public oboe::AudioStreamDataCallback{
public:
    oboe::DataCallbackResult
    onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) override{
        memset(audioData,0,numFrames*sizeof(int16_t)*2);
        for(int32_t i=0;;i++){
            if(i>=playStatusVector.size())break;
            if(mixData(audioData, numFrames,&playStatusVector.at(i))){
                playStatusVector.erase(playStatusVector.begin()+i--);
            }
        }
        return oboe::DataCallbackResult::Continue;
    }
};

static SoundCallback callback;

bool mixData(void* audioDataBuffer,int32_t numFrames,PlayStatus* playStatus){
    for(int32_t i=0;i<numFrames;i++){
        PlayCache playCache=playCaches[playStatus->indexOfCache];
        uint32_t size=playCache.size;
        if(size/sizeof(int32_t)<=playStatus->readBytes)return true;
        int32_t addon=static_cast<int32_t*>(playCache.buffer)[playStatus->readBytes];
        ((int32_t*)audioDataBuffer)[i]+=static_cast<int32_t>(addon);
        playStatus->readBytes+=1;
    }
    return false;
}

void init(int32_t framesPerBuffer,int32_t sampleRate){
    oboe::AudioStreamBuilder builder;
    builder.setDirection(oboe::Direction::Output);
    builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
    builder.setSharingMode(oboe::SharingMode::Exclusive);
    builder.setFormat(oboe::AudioFormat::I16);
    builder.setChannelCount(oboe::ChannelCount::Stereo);
    builder.setSampleRate(sampleRate);
    builder.setFramesPerDataCallback(framesPerBuffer);
    builder.setUsage(oboe::Usage::Game);
    builder.setDataCallback(&callback);
    oboe::Result result=builder.openStream(stream);
    if (result!=oboe::Result::OK){
        return;
    }
    stream->requestStart();
    __android_log_print(ANDROID_LOG_DEBUG,"Cirno icy","Stream started");
}

void allocFromFile(const char* fileName,PlayCache* playCache){
    struct stat statBuffer{};
    FILE* file=fopen(fileName,"rb");
    stat(fileName,&statBuffer);
    uint32_t size=statBuffer.st_size;
    playCache->size=size;
    void *buffer= malloc(sizeof(u_char) * size);
    memset(buffer,0,sizeof(u_char)*size);
    while (!feof(file)){
        fread(buffer,sizeof(u_char),size,file);
    }
    playCache->buffer=buffer;
    fclose(file);
}

extern "C" JNIEXPORT void JNICALL Java_han_chensing_latos_sound_CirnoSound_playSound(
        JNIEnv*, jobject,jint cacheNum) {
    PlayStatus status;
    status.readBytes=0;
    status.indexOfCache=cacheNum;
    playStatusVector.push_back(status);
}

extern "C" JNIEXPORT void Java_han_chensing_latos_sound_CirnoSound_cacheSound(
        JNIEnv* env,jobject,jint cacheNum,jstring filePath){
    jboolean isCopied=JNI_FALSE;
    const char* filePathCStr=env->GetStringUTFChars(filePath,&isCopied);
    allocFromFile(filePathCStr,&playCaches[cacheNum]);
    env->ReleaseStringUTFChars(filePath,filePathCStr);
}

extern "C" JNIEXPORT void JNICALL Java_han_chensing_latos_sound_CirnoAudio_init(
        JNIEnv*, jobject,jint capacity,jint framesPerBuffer,jint sampleRate) {
    playCachesCapacity=capacity;
    playCaches=(PlayCache*)malloc(capacity*sizeof(PlayCache));
    init(framesPerBuffer,sampleRate);
}

extern "C" JNIEXPORT void JNICALL Java_han_chensing_latos_sound_CirnoAudio_disposeNative(JNIEnv*,jobject){
    stream->requestStop();
    for(int32_t i=0;i<playCachesCapacity;i++){
        free(playCaches[i].buffer);
    }
    free(playCaches);
    __android_log_print(ANDROID_LOG_DEBUG,"Cirno icy","dispose audio");
}

extern "C" JNIEXPORT jint JNICALL Java_han_chensing_latos_AndroidV_getAudioBufferUsage(JNIEnv*,jclass) {
    return (jint)playStatusVector.size();
}